<?php
/**
 * App 微信用户
 *
 * Author: Zeon
 * Date: 2018/4/12
 * Created by Panxsoft.
 */
namespace App\Http\Controllers\App;

use App\Exceptions\ApiErrCode;
use App\Http\Controllers\ApiController;
use App\Http\Requests\App\WechatUser\UpdateOrCreateRequest;
use App\Http\Requests\App\UserLoginPollRequest;
use App\Http\Requests\App\UserLoginQrCodeRequest;
use App\Models\Admin\StoreAdmin;
use App\Models\Customer\Customer;
use App\Models\OpenWechat\WechatTemplateNotice;
use App\Models\OpenWechat\Authorizer;
use App\Models\OpenWechat\ScanEvent;
use App\Models\OpenWechat\WechatUser;
use App\Models\Panx\SkinDetector;
use App\Models\Store\CamSkinRecord;
use App\Models\Store\SkinRecord;
use App\Models\Store\Store;
use App\Models\Store\StorePivotWechatUser;
use App\Services\OpenWechat\AppFactory;
use App\Services\OpenWechat\AuthorizerRepository;
use App\Services\OpenWechat\QrCode;
use App\Services\OpenWechat\ScanEventHandler;
use App\Services\OpenWechat\TemplateNotice;
use App\Services\OpenWechat\WechatErrorCode;
use App\Services\Presenters\SkinRecordPresenter;
use App\Services\OpenWechat\CustomerTemplateNotice;
use App\Services\Repositories\WechatTemplateNoticeRepository;
use App\Services\Repositories\SkinRecord\SkinRecordRepository;
use App\Services\Utils\Guard;
use App\Services\Utils\RequestParam;
use App\Services\Utils\SimpleQrCode;
use App\Services\Utils\Transaction;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Response;

class WechatUserController extends ApiController
{
    const ROUTES = [
        'show'  => 'api.app.v2-3.user.skin.record.show',
        'login' => 'api.app.v2-3.user.skin.record.login.qrcode',
        'poll'  => 'api.app.v2-3.user.skin.record.login.poll',
        'updateOrCreate'  => 'api.app.user.update-or-create',
    ];

    const WX_SKIN_RECORDS_URL_FORMAT = '%s/wx-system/?%s#/report';            // 测肤的模板地址
    const URL = '%s/wx-system?scan_event_id=%s&lang=%s&customer_id=%d#login'; // 无公众号的h5扫码登录url


    /**
     * 保存微信用户信息 - 天瓷
     * @test
     *
     * @param UpdateOrCreateRequest $request
     * @return array|Response
     * @throws \Exception
     */
    public function updateOrCreate(UpdateOrCreateRequest $request)
    {
        $deviceAccount = Guard::deviceAccount();

        /** @var Store $store */
        $store = $deviceAccount->store;

        $authorizer_id = $store->customer->authorizer->id ?? 0;

        $data = $this->assembleUpdateOrCreateData($request, $authorizer_id);

        $wechat_user = Transaction::handle(function () use ($request,$data,$store){
            (new WechatUser)->updateOrCreate([
                'openid' => $request->openid
            ], $data);

            $wechat_user = WechatUser::findByOpenId($request->openid);

            $store->wechatUsers()->syncWithoutDetaching([$wechat_user->id]);

            return $wechat_user;
        });

        return $this->response(compact('wechat_user'));
    }
    
    /**
     * @deprecated - App 接口不清理 - since v2.3 查询指定微信用户的最近肤质检测数据
     *
     * @see V2_3ShowSkinRecord()
     *
     * @param int $wechat_user_id
     * @return array|Response
     */
    public function showSkinRecord($wechat_user_id)
    {
        $latest_type_groups = $this->queryLatestUniqueTypeSkinRecords($wechat_user_id);

        $cam_skin_record = $this->assembleCamSkinRecord($latest_type_groups);
        $wax_skin_record = $this->assembleWaxSkinRecord($latest_type_groups);
        $hm_skin_record = $this->assembleHmSkinRecord($latest_type_groups);

        $skin_record = [
            'cam' => $cam_skin_record,
            'wax' => $wax_skin_record,
            'hm'  => $hm_skin_record,
        ];

        return $this->response(compact('skin_record'));
    }

    /**
     * 测肤记录 - 按照检测项或者部位分组，在按照测肤记录的正序排列。
     * @version v2.3
     * @test
     *
     * @param $wechat_user_id
     * @param SkinRecordRepository $skinRecordRepository
     *
     * @return array|Response
     */
    public function V2_3ShowSkinRecord($wechat_user_id,
                                       SkinRecordRepository $skinRecordRepository)
    {
        // App 显示用户数据暂时不过滤对应登录账号的店铺，App 上可以出现其他店铺/商家的用户测肤数据
        $skin_record_all = (new SkinRecord)
            ->whereIn('skin_detector_id', [SkinDetector::ID_HM,SkinDetector::ID_WAX])
            ->where('wechat_user_id', $wechat_user_id)
            ->get();

        $customer_id = Guard::deviceAccount()->customer_id;

        $hm_skin_record_ids = $skin_record_all->where('skin_detector_id',SkinDetector::ID_HM)->pluck('id')->all();
        $wax_skin_record_ids = $skin_record_all->where('skin_detector_id',SkinDetector::ID_WAX)->pluck('id')->all();

        $hm_skin_record = $skinRecordRepository->getSkinRecords(SkinDetector::ID_HM, $hm_skin_record_ids, $customer_id);
        $wax_skin_record = $skinRecordRepository->getSkinRecords(SkinDetector::ID_WAX, $wax_skin_record_ids, $customer_id);

        $skin_record = [
            'wax' => $wax_skin_record,
            'hm'  => $hm_skin_record,
        ];

        return $this->response(compact('skin_record'));
    }

    /**
     * 返回 用户测肤扫码登录的 QrCode二维码
     * @test
     *
     * 有公众号：用户
     * @used-by ScanEventHandler::handlePadUserLogin() - 微信用户扫码事件处理方法
     *
     * @used-by \App\Http\Controllers\Wechat\WechatUserController::patchPhone() - 无公众号h5处理方法
     *
     * @param UserLoginQrCodeRequest $request
     * @return array|\Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
     * @throws \Exception
     */
    public function loginQrCode(UserLoginQrCodeRequest $request)
    {
        $deviceAccount = Guard::deviceAccount();

        /** @var Store $store */
        $store = $deviceAccount->store;
            $this->checkItemExist($store, trans('app.store_error'));

        /** @var Authorizer $authorizer */
        $authorizer = $store->getAuthorizer();
    
        $scan_event = ScanEvent::createAppLoginEvent($store, $deviceAccount, $request->staff_id);

        if ($authorizer) { // 有公众号的处理
            (new AuthorizerRepository($authorizer))->checkIsTypeService();

            $qr_result = $this->createWechatQrcode($authorizer, $scan_event);
        } else {  // 没有公众号的处理
            $qr_result = $this->createH5PageQrcode($scan_event, $deviceAccount->language, $store->customer_id);
        }

        $url = $qr_result['url'];
        $scan_event_id = $scan_event->id;
        $code_img_url = $qr_result['code_img_url'];

        if ($request->type === 'png') return $this->responseImage(SimpleQrCode::toImage($url));

        return $this->response(compact('url', 'scan_event_id', 'code_img_url'));
    }

    /**
     * 美妆镜生成用户登录二维码后，Pad轮询判断用户是否扫码接口。
     * @see loginQrCode()
     * @test
     *
     * @param UserLoginPollRequest $request
     * @return array|Response
     * @throws \App\Exceptions\ApiException
     * @throws \Exception
     */
    public function loginPoll(UserLoginPollRequest $request)
    {
        /** @var ScanEvent $scan_event */
        $scan_event = (new ScanEvent)->find($request->scan_event_id);
            $this->checkItemExist($scan_event, trans('app.scan_event_error'));
            $this->checkIsTrue(
                $scan_event->isScanned(), trans('app.check_can_operate'),
                ApiErrCode::APP_LOGIN_SCAN_EVT_NOT_FOUND
            );

        $wechat_user_id = $scan_event->value;
        /** @var WechatUser $wechat_user */
        $wechat_user = WechatUser::find($wechat_user_id);
            $this->checkItemExist($wechat_user, trans('app.wechat_user_error'));

        $wechat_user->append('age');
        $wechat_user->addHidden('authorizer');

        $customer_id = $scan_event->data['customer_id'] ?? 0;
        $wechat_user->skin_record_count = $this->getWechatUserSkinRecordCount($customer_id, $wechat_user_id);

        /** 记录store_pivot_wechat_user表的信息 */
        $this->saveStorePivotWechatUser($wechat_user_id);

        if ($wechat_user->authorizer_id &&
            $scan_event->isPadUserLogin() &&
            ! $scan_event->isTemplateNoticeSent()) {  // 有公众号,用户是pad登录类型
            /** 给业务员发送模板消息 */
            /** @var array|null $notice_res */
            $notice_res = $this->sendStaffServeNotice($scan_event, $wechat_user);

            $scan_event->updateDataTemplateSentResponse($notice_res);
        }

        return $this->response(compact('wechat_user'));
    }

    /**
     *  发送模板消息
     * @param $appid
     * @param StoreAdmin $staff
     * @param WechatUser $login_user
     * @param $template_id
     * @param $notice_data
     * @return array|null
     * @throws \Exception
     */
    private function sendTemplateNotice($appid, StoreAdmin $staff, WechatUser $login_user, $template_id,$notice_data): ?array
    {
//        $this->checkIsTrue($staff->wechatUser->subscribe === WechatUser::SUBSCRIBED, '微信用户取消了关注');
        $staff_openid = $staff->wechatUser->openid;

        $data = $notice_data;

        $query = http_build_query([
            'openid'   => $login_user->openid,
            'appid'    => $appid,
            /**
             * 此值暂时没用，后续可能加入服务评价等
             */
            'staff_id' => $staff->id
        ]);

        $url = sprintf(self::WX_SKIN_RECORDS_URL_FORMAT, config('app.url'), $query);

        return (new TemplateNotice($appid))->reserveNotice($staff_openid, $url, $data, $template_id);
    }

    /**
     * @param $wechat_user_id
     * @return bool
     */
    private function saveStorePivotWechatUser($wechat_user_id)
    {
        $store = Guard::deviceAccount()->store;

        $store_pivot_wechat_user = (new StorePivotWechatUser)
            ->where('store_id', $store->id)
            ->where('wechat_user_id', $wechat_user_id)
            ->first();

        if (! $store_pivot_wechat_user) {
            $store->wechatUsers()->attach($wechat_user_id);
        }

        return true;
    }

    /**
     * @param ScanEvent $scan_event
     * @param WechatUser $login_user
     * @return array|null
     * @throws \Exception
     */
    private function sendStaffServeNotice(ScanEvent $scan_event, WechatUser $login_user): ?array
    {
        $store_admin_id = $scan_event->getPadLoginServeStaffId();
        if (! $store_admin_id) {
            return null;
        }

        /** @var StoreAdmin $store_admin */
        $store_admin = StoreAdmin::find($store_admin_id);
        $authorizer = $store_admin->wechatUser->authorizer ?? null;
        if (! $authorizer) {
            return null;
        }

        $appid = $authorizer->authorizer_appid;
        /** 查找公众号的模板消息编号，如无，保存新的模板消息编号 */
        $template_id = (new WechatTemplateNoticeRepository)
            ->getAuthorizerTemplateId(
                $authorizer,WechatTemplateNotice::ID_SHORT_STORE_ADMIN
            );
        $this->checkIsTrue($template_id, trans('app.custom_value_template_error'));

        $notice_data = $this->getNoticeData($authorizer,$login_user);

        return $this->sendTemplateNotice($appid, $store_admin, $login_user, $template_id,$notice_data);
    }

    public function getNoticeData(Authorizer $authorizer,WechatUser $login_user)
    {
        $app = (new AppFactory())->authorizerApp($authorizer->authorizer_appid);

        if ( CustomerTemplateNotice::isIndustryIt($app) )
        {
            $notice_data = [
                'first'    => trans('app.template_first', ['user'=>$login_user->decodeNickName()]),
                'keyword1' => trans('app.template_keyword1'),
                'keyword2' => '肤质检测', // trans
                'remark'   => trans('app.template_remark'),
            ];

        }else{
            $notice_data = [
                'first'    => trans('app.template_first', ['user'=>$login_user->decodeNickName()]),
                'keyword1' => trans('app.template_keyword1'),
                'keyword2' => trans('app.template_keyword2'),
                'remark'   => trans('app.template_remark'),
            ];
        }

        return $notice_data;
    }

    /**
     * 已用 filterTypeSkinRecord 方法代替
     *
     * @param $wechat_user_id
     * @param $skin_detector_id
     * @return mixed
     */
    private function latestTypeSkinRecord($wechat_user_id, $skin_detector_id)
    {
        return SkinRecord::where(compact('wechat_user_id', 'skin_detector_id'))
            ->latest()
            ->first();
    }

    /**
     * @param Collection $skin_records
     * @param string|int $skin_detector_id
     * @return SkinRecord|null
     */
    private function filterTypeSkinRecord(Collection $skin_records, $skin_detector_id): ?SkinRecord
    {
        return $skin_records->where('skin_detector_id', '=', $skin_detector_id)
            ->first();
    }

    /**
     * @param $skin_records
     * @return array
     */
    private function assembleCamSkinRecord(Collection $skin_records): ?array
    {
        $skin_record = $this->filterTypeSkinRecord(
            $skin_records, SkinDetector::ID_CAMERA);
        /** @var CamSkinRecord $cam_skin_record */
        if ($skin_record) {
            $cam_skin_record = $skin_record->camSkinRecords()->first();
            $cam_skin_record = SkinRecordPresenter::assembleDetailCamSkinRecord($cam_skin_record);
        } else {
            $cam_skin_record = null;
        }

        return $cam_skin_record;
    }

    /**
     * @deprecated
     * @param $skin_records
     * @return array
     */
    private function assembleWaxSkinRecord(Collection $skin_records): ?array
    {
        $skin_record = $this->filterTypeSkinRecord(
            $skin_records, SkinDetector::ID_WAX);
        /** @var CamSkinRecord $cam_skin_record */
        if ($skin_record) {
            $wax_skin_records = $skin_record->waxSkinRecords;
            $wax_skin_record = SkinRecordPresenter::assembleDetailWaxSkinRecords($wax_skin_records);
        } else {
            $wax_skin_record = null;
        }

        return $wax_skin_record;
    }

    /**
     * @deprecated ()
     * @param $skin_records
     * @return array
     */
    private function assembleHmSkinRecord(Collection $skin_records)
    {
        $skin_record = $this->filterTypeSkinRecord(
            $skin_records, SkinDetector::ID_HM);
        /** @var CamSkinRecord $cam_skin_record */
        if ($skin_record) {
            $hm_skin_records = $skin_record->hmSkinRecords;
            $hm_skin_record = SkinRecordPresenter::assembleDetailHmSkinRecords($hm_skin_records);
        } else {
            $hm_skin_record = null;
        }

        return $hm_skin_record;
    }

    /**
     * @param Authorizer $authorizer
     * @param ScanEvent $scan_event
     *
     * @return array
     * @throws \Exception
     */
    private function createWechatQrcode(Authorizer $authorizer, ScanEvent $scan_event)
    {
        $qr_result = QrCode::generate(
            $authorizer->authorizer_appid,
            $scan_event->getSceneValue()
        );
            $this->checkQrResult($qr_result);

        $qr_result['code_img_url'] = QrCode::ticketToQrCodeUrl($authorizer->authorizer_appid, $qr_result['ticket']);

        return $qr_result;
    }

    /**
     * @param ScanEvent $scan_event
     * @param $language
     * @param $customer_id
     * @return mixed
     */
    private function createH5PageQrcode(ScanEvent $scan_event, $language, $customer_id)
    {
        $scan_event_id = $scan_event->id;

        $url = sprintf(self::URL, config('app.url'), $scan_event_id, $language, $customer_id);

        $result['url'] = $url;
        $result['code_img_url'] = $url;

        return $result;
    }

    /**
     * groupBy 的使用虽然减少了查询次数，但较难理解，
     * groupBy 的 orderBy 不是在 group 之前进行的，而是在之后，所以不能 orderBy updated_at DESC 提前排序
     * 只能 max(id) as id 这样，但如果最大的 id 不是最新数据时，
     * 只能 max(updated_at) as update_at 再 innerJoin 本表，
     * 或 SELECT FROM (Sub Query) GroupBy， 查询SQL会更复杂
     * @see latestTypeSkinRecord() - 如果后面有扩展需示不合适，再使用原来的方法多次查询
     *
     * @param $wechat_user_id
     * @return Collection|SkinRecord[]
     */
    private function queryLatestUniqueTypeSkinRecords($wechat_user_id): Collection
    {
        return SkinRecord::where(compact('wechat_user_id'))
            ->selectRaw('MAX(id) as id, skin_detector_id')
            ->groupBy('skin_detector_id')
            ->get();
    }

    /**
     * @param array $qr_result
     * @throws \Exception
     */
    private function checkQrResult(array $qr_result): void
    {
        if (! isset($qr_result['errcode'])) {
            return;
        }

        // customize error response by err code
        switch ($qr_result['errcode']) {
            case WechatErrorCode::API_UNAUTHORIZED :
                $qr_result['errmsg'] = trans('wechat.error.qr_code_api_unauthorized');
                break;
            default :
                break;
        }

        $this->checkFailed($qr_result['errmsg'], $qr_result['errcode']);
    }

    /**
     * @param UpdateOrCreateRequest $request
     * @param $authorizer_id
     * @return array
     */
    private function assembleUpdateOrCreateData(UpdateOrCreateRequest $request, $authorizer_id): array
    {
        $field = array_keys($request->rules());

        $data = RequestParam::normalize($field, [
            'authorizer_id' => $authorizer_id,
            'customer_id'   => 0,
        ]);

        if ($request->has('nickname')) {
            $data ['nickname_unicode'] = json_encode($request->nickname);
        }
        return $data;
    }

    private function getWechatUserSkinRecordCount(int $customer_id, int $wechat_user_id): int
    {
        if (! $customer_id) {
            return 0;
        }

        /** @var Customer $customer */
        $customer = (new Customer)->find($customer_id);
        if (! $customer) {
            return 0;
        }

        $customer_store_ids = $customer->stores->modelKeys();

        return (new SkinRecord)
            ->whereIn('store_id', $customer_store_ids)
            ->where(compact('wechat_user_id'))
            ->count();
    }
}
